/*
 * File:   main.c
 * Author:
 *
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>



#define MAX_PROCESS 5
#define MAX_REQUESTS 5
#define MAX_STEPS 10000
//one tick in ms
#define TIME_CYCLE 1000000
#define TOTAL_TIME_TICKS 10

void *Timer(void *threadid);
void *DiskIO(void *inter);
void *VideoIO(void *inter);
void *KeyboardIO(void *inter);
void printStatesQueue();

enum process_interrupt {true, false};

enum p_c_semaphore {produced, consumed} semaphore = consumed;

enum io_interrupt {disk, video, keyboard, producer, consumer, calc};

enum interrupt_enum {timer,
                     disk_request,
                     disk_interrupt,
                     video_request,
                     video_interrupt,
                     keyboard_request,
                     keyboard_interrupt,
                     producer_interrupt,
                     consumer_interrupt,
                     calc_interrupt} interrupt;

enum states {running,ready, interrupted, blocked};

typedef struct process_str
{
    int no_steps;
    int no_requests;
    struct requsets_str *requests;
} ProcessStr;

ProcessStr* create_process()
{
   ProcessStr *process = (ProcessStr *) malloc(sizeof(*process));
    if (process == NULL)
    {
        fprintf(stderr, "create_process() : malloc error");
        return NULL;
    }

   process->no_steps = rand()%(MAX_STEPS - 1000) + 1000 ;
   process->no_requests = 0;
   process->requests = 0;
   return process;
}



typedef struct pcb_str
{
    int pid;
    int next_step;
    enum states state;      //0=running, 1=ready, 2=interrupted, 3=blocked
    int waiting_on; //which queue is it in if it is waiting on something
    int owns;       //which semaphore does it own
    struct process_str *process;
    struct pcb_str *next;
} PCBStr;

PCBStr * create_pcb(ProcessStr *process)
{
    PCBStr *pcb = (PCBStr *) malloc(sizeof(*pcb));
    if (pcb == NULL)
    {
        fprintf(stderr, "create_pcb(ProcessStr *process) : malloc error");
        return NULL;
    }

    pcb->pid = -1;
    pcb->next_step = 1;
    pcb->state = 1;
    pcb->waiting_on = -1;
    pcb->owns = -1;
    pcb->process = process;
    pcb->next = 0;

    return pcb;
}


typedef struct requsets_str
{
    int step;
    enum io_interrupt io_int;
} Request;

Request* create_request()
{
    Request *req = (Request *) malloc(sizeof(*req));
    if (req == NULL)
    {
        fprintf(stderr, "create_request() : malloc error");
        return NULL;
    }

    return req;
}

void printQueue(PCBStr *the_head);

void inqueue(PCBStr *head, PCBStr *node);

PCBStr *dequeue(PCBStr *head);

ProcessStr process[MAX_PROCESS], *idle, *keyboard_p, *producer_p, *consumer_p;
PCBStr pcb[MAX_PROCESS], *idle_pcb, *keyboard_pcb, *producer_pcb, *consumer_pcb;
PCBStr ready_queue_head, blocked_video_q, blocked_keyboard_q, blocked_disk_q, blocked_producer_q, blocked_consumer_q;
PCBStr *running_process;
Request requests[MAX_PROCESS + 10][MAX_REQUESTS];

pthread_mutex_t scheduler_lock;
pthread_mutex_t interrupt_lock;
pthread_mutex_t cpu_lock;
pthread_mutex_t number_lock;

int number_of_ticks = 0;
int p_c_number = 0;

double f0 = 0;
double f1 = 1;
double fn;
int f_number = 1;

void initializeProcesses()
{
    srand(time(NULL));
    
    ready_queue_head.next = 0;
    blocked_video_q.next = 0;
    blocked_disk_q.next = 0;
    blocked_keyboard_q.next = 0;
    blocked_producer_q.next = 0;
    blocked_producer_q.pid = -1;
    blocked_consumer_q.next = 0;
    blocked_consumer_q.pid = -1;

    idle = create_process();
    idle->no_steps = rand()%(MAX_STEPS - 1000) + 1000 ;
    idle->requests = requests[MAX_PROCESS];

    idle_pcb = create_pcb(idle);

    ready_queue_head = *idle_pcb;


    int i;
    
    for(i = 0; i<MAX_PROCESS; i++)
    {
        process[i].no_steps = rand()%(MAX_STEPS - 1000) + 1000 ;
        process[i].no_requests = rand()%MAX_REQUESTS +1;
        process[i].requests = requests[i];
        pcb[i].pid = i;
        pcb[i].next_step = 1;
        pcb[i].state = 1;
        pcb[i].process = &process[i];
        inqueue(&ready_queue_head, &pcb[i]);
    }

    int k;

    Request r;
    for(i = 0; i<MAX_PROCESS; i++)
    {
        for(k = 0; k < process[i].no_requests - 1; k++)
        {
            
            r.io_int = rand()%2;
            r.step = rand()%process[i].no_steps;
            process[i].requests[k] = r;
        }
    }

    //keyboard listener process
    keyboard_p = create_process();
    keyboard_p->no_requests = 1;
    keyboard_p->requests = requests[MAX_PROCESS + 1];
    r.io_int = 2;
    r.step = rand()%keyboard_p->no_steps;
    keyboard_p->requests[0] = r;
    
    keyboard_pcb = create_pcb(keyboard_p);
    keyboard_pcb->pid = 101;
    inqueue(&ready_queue_head, keyboard_pcb);

    //producer

    r.io_int = 3;
    r.step = rand()%process[1].no_steps;
    process[1].requests[0] = r;

    //consumer
    r.io_int = 4;
    r.step = rand()%process[2].no_steps;
    process[2].requests[0] = r;

    //calc
    r.io_int = 5;
    r.step = rand()%process[3].no_steps;
    process[3].requests[0] = r;

}

void scheduler()
{
    pthread_mutex_lock (&scheduler_lock);
    printf("the interrupt occured for pid = %i at step %d \n",running_process->pid,running_process->next_step);
    switch(interrupt)
    {
        case timer:
            printf("timer interrupt, context switch from pid=%i",running_process->pid);
            running_process->state = ready;
            //not idle process
            if(running_process->pid >-1)
            {
                inqueue(&ready_queue_head,running_process);             
            }
            running_process = dequeue(&ready_queue_head);
            running_process->state = running;
            printf(" to pid= %i\n\n",running_process->pid);
            printStatesQueue();
            break;

        case disk_request:
            printf("disk request for pid=%i move it into blocked queue and",running_process->pid);
            running_process->next_step++;
            inqueue(&blocked_disk_q, running_process);
            running_process->state = blocked;
            running_process = dequeue(&ready_queue_head);
            running_process->state = running;
            printf(" switch to pid= %i\n\n",running_process->pid);
            printStatesQueue();
            break;

        case video_request:
            printf("video request for pid=%i move it into blocked queue and",running_process->pid);
            running_process->next_step++;
            inqueue(&blocked_video_q,running_process);
            running_process->state = blocked;
            running_process = dequeue(&ready_queue_head);
            running_process->state = running;
            printf(" switch to pid= %i\n\n",running_process->pid);
            printStatesQueue();
            break;

        case disk_interrupt:
            printf("disk interrupt for pid=%i ",running_process->pid);
            PCBStr *t1 = (PCBStr *) malloc(sizeof(*t1));
                    t1=dequeue(&blocked_disk_q);
            inqueue(&ready_queue_head, t1 );
            printf(" move  pid =%i from blocked queue and continue \n\n",t1->pid);
            break;

        case video_interrupt:
            printf("video interrupt for pid=%i",running_process->pid);
            PCBStr *t2 = (PCBStr *) malloc(sizeof(*t2));
                    t2=dequeue(&blocked_video_q);
            inqueue(&ready_queue_head, t2 );
            printf(" move  pid =%i from blocked queue and continue \n\n",t2->pid);
            break;

        case keyboard_request:
            printf("keyboard request for pid=%i",running_process->pid);
            running_process->next_step++;
            inqueue(&blocked_keyboard_q,running_process);
            running_process->state = blocked;
            running_process = dequeue(&ready_queue_head);
            running_process->state = running;
            printf(" switch to pid= %i\n\n",running_process->pid);
            printStatesQueue();
            break;

        case keyboard_interrupt:
            printf("keyboard interrupt for pid=%i",running_process->pid);
            PCBStr *t3 = (PCBStr *) malloc(sizeof(*t3));
                    t3=dequeue(&blocked_keyboard_q);
            inqueue(&ready_queue_head, t3 );
            printf(" move  pid =%i from blocked queue and continue \n\n",t3->pid);
            break;

        case producer_interrupt:
            printf("producer interrupt for pid=%i number = %i not consumed yet",running_process->pid, p_c_number);
            inqueue(&blocked_producer_q,running_process);
            running_process->state = blocked;
            running_process = dequeue(&ready_queue_head);
            running_process->state = running;
            printf(" switch to pid= %i\n\n",running_process->pid);
            printStatesQueue();
            break;

        case consumer_interrupt:
            printf("consumer interrupt for pid=%i number = %i already consumed",running_process->pid, p_c_number);
            inqueue(&blocked_consumer_q,running_process);
            running_process->state = blocked;
            running_process = dequeue(&ready_queue_head);
            running_process->state = running;
            printf(" switch to pid= %i\n\n",running_process->pid);
            printStatesQueue();
            break;

        default: printf("unknown request for pid=%i",running_process->pid);

    }
    pthread_mutex_unlock (&scheduler_lock);
}
/*
 *
 */
int main(int argc, char** argv)
{
    pthread_mutex_init(&scheduler_lock, NULL);
    pthread_mutex_init(&cpu_lock, NULL);

    initializeProcesses();

    printQueue(&ready_queue_head);

    running_process = dequeue(&ready_queue_head);
    running_process->state = running;

    enum process_interrupt proc_int = false;

    pthread_t threads;
    pthread_t timer_thread;

    if (pthread_create(&timer_thread, NULL, Timer, (void *)&proc_int))
    {
         printf("ERROR; return code from pthread_create() ");
         exit(-1);
    }
   
    while(number_of_ticks < TOTAL_TIME_TICKS)
    {

        pthread_mutex_lock (&cpu_lock);
        running_process->next_step = running_process->next_step % running_process->process->no_steps;

        int k;
        for(k = 0; k < running_process->process->no_requests; k++)
        {
            if(running_process->process->requests[k].step == running_process->next_step)
            {
                switch(running_process->process->requests[k].io_int)
                {
                   case 0:
                            pthread_mutex_lock (&interrupt_lock);
                            interrupt = disk_request;                            
                            scheduler();
                            pthread_mutex_unlock (&interrupt_lock);
                            if (pthread_create(&threads, NULL, DiskIO, (void *)&proc_int))
                            {
                                printf("ERROR; return code from pthread_create() ");
                                exit(-1);
                            }
                            break;
                   case 1:
                            pthread_mutex_lock (&interrupt_lock);
                            interrupt = video_request;
                            scheduler();
                            pthread_mutex_unlock (&interrupt_lock);
                            if (pthread_create(&threads, NULL, VideoIO, (void *)&proc_int))
                            {
                                printf("ERROR; return code from pthread_create() ");
                                exit(-1);
                            }
                            break;
                   case 2:
                            pthread_mutex_lock (&interrupt_lock);
                            interrupt = keyboard_request;
                            scheduler();
                            pthread_mutex_unlock (&interrupt_lock);
                            if (pthread_create(&threads, NULL, KeyboardIO, (void *)&proc_int))
                            {
                                printf("ERROR; return code from pthread_create() ");
                                exit(-1);
                            }
                            break;

                   case 3:                       
                            if(semaphore == produced)
                            {
                                pthread_mutex_lock (&interrupt_lock);
                                interrupt = producer_interrupt;
                                scheduler();
                                pthread_mutex_unlock (&interrupt_lock);
                            }
                            else
                            {
                                semaphore = produced;
                                p_c_number = rand()%100;
                                printf("Producer produced number = %i ,", p_c_number);
                                PCBStr *t10 = (PCBStr *) malloc(sizeof(*t10));
                                t10 = dequeue(&blocked_consumer_q);
                                if(t10->pid > -1)
                                {
                                    inqueue(&ready_queue_head, t10 );
                                    printf(" move  consumer pid =%i from blocked queue and continue \n\n",t10->pid);
                                }
                                else
                                    printf("\n\n");
                            }
                            break;

                   case 4:
                            if(semaphore == consumed)
                            {
                                pthread_mutex_lock (&interrupt_lock);
                                interrupt = consumer_interrupt;                                
                                scheduler();
                                pthread_mutex_unlock (&interrupt_lock);
                            }
                            else
                            {
                                semaphore = consumed;
                                char *str="odd";
                                if(p_c_number%2 == 0)
                                    str = "even";
                                printf("Consumer: produced number %i is %s ", p_c_number, str);
                                PCBStr *t20 = (PCBStr *) malloc(sizeof(*t20));
                                t20 = dequeue(&blocked_producer_q);
                                if(t20->pid > -1)
                                {
                                    inqueue(&ready_queue_head, t20 );
                                    printf(" move  producer pid =%i from blocked queue and continue \n\n",t20->pid);
                                }
                                else
                                    printf("\n\n");

                            }
                            break;

                   case 5:
                            fn = f0 + f1;
                            f0 = f1;
                            f1 = fn;
                            f_number++;

                            break;

                   default: printf("unknown routine");
                }
                break;
            }
        }

        //usleep(1);
        running_process->next_step++;
        pthread_mutex_unlock (&cpu_lock);
    }

    free(idle);

    printf("Fib(%i) = %.0lf \n",f_number,fn);
    //kill all threads (main included)
    pthread_kill(timer_thread, SIGTERM);
    //printf("success");
    return (EXIT_SUCCESS);
}

void *Timer(void *inter)
{
    
   while(1)
   {

        usleep(TIME_CYCLE);
        number_of_ticks++;
        pthread_mutex_lock (&interrupt_lock);
        interrupt = timer;
        running_process->state = 1;
        scheduler();
        pthread_mutex_unlock (&interrupt_lock);

   }

   pthread_exit(NULL);
}

void *DiskIO(void *inter)
{
    usleep(rand()%(TIME_CYCLE*10) + TIME_CYCLE);
    pthread_mutex_lock (&interrupt_lock);
    interrupt = disk_interrupt;
    running_process->state = 2;
    scheduler();
    pthread_mutex_unlock (&interrupt_lock);
    pthread_exit(NULL);
}

void *VideoIO(void *inter)
{
    usleep(rand()%(TIME_CYCLE*20) + TIME_CYCLE);
    pthread_mutex_lock (&interrupt_lock);
    interrupt = video_interrupt;
    running_process->state = 2;
    scheduler();
    pthread_mutex_unlock (&interrupt_lock);

    pthread_exit(NULL);
}

void *KeyboardIO(void *inter)
{
    char ch = getch();
    pthread_mutex_lock (&interrupt_lock);
    interrupt = keyboard_interrupt;
//    printf("the keyboard interrupt occured for pid = %i at step %d \n",running_process->pid,running_process->next_step);
    printf("You pressed %c \n", ch);
    running_process->state = 3;
    scheduler();
    pthread_mutex_unlock (&interrupt_lock);
    pthread_exit(NULL);
}


void printQueue(PCBStr *the_head)
{
    PCBStr *iterator = the_head->next;

    while(iterator !=0)
    {
        //printf("new print pcb id %i \n", iterator->pid);
        printf("pid= %i, steps = %i requests = %i \n", iterator->pid, iterator->process->no_steps,iterator->process->no_requests);
        int k;
        for(k = 0; k < iterator->process->no_requests; k++)
        {
            char *str;

            switch(iterator->process->requests[k].io_int)
            {
               case 0: str = "disk"; break;
               case 1: str = "video"; break;
               case 2: str = "keyboard"; break;
               case 3: str = "producer"; break;
               case 4: str = "consumer"; break;
               case 5: str = "calc Fib"; break;
               default: str = "unknown";
            }
            printf("       |requests %s at step %i\n", str,iterator->process->requests[k].step);
        }

        iterator = iterator->next;
    }
    printf("\n\n");
}

void printStatesQueue()
{
    char *str;
    int i;
    for(i = 0; i<MAX_PROCESS; i++)
    {
        switch(pcb[i].state)
        {
           case 0: str = "running"; break;
           case 1: str = "ready"; break;
           case 2: str = "iterrupted"; break;
           case 3: str = "blocked"; break;

           default: str = "unknown";
        }
        printf("pid = %i state: %s ,",pcb[i].pid, str);

    }

        switch(keyboard_pcb->state)
        {
           case 0: str = "running"; break;
           case 1: str = "ready"; break;
           case 2: str = "iterrupted"; break;
           case 3: str = "blocked"; break;

           default: str = "unknown";
        }

    printf("pid = %i state: %s \n\n", keyboard_pcb->pid, str);
}

//inqueue to tail
void inqueue(PCBStr *head, PCBStr *node)
{
    node->next = 0;
    PCBStr *iter = head;

    while(iter->next !=0)
            iter = iter->next;

    iter->next = node;
}

//dequeue from head
PCBStr *dequeue(PCBStr *head)
{
    PCBStr *temp;
    temp = head->next;

    if(temp == 0)
    {
        head->next = 0;
        return head;
    }

    head->next = head->next->next;
    temp->next = 0;
    return temp;
}

//size
int sizeQueue(PCBStr *head)
{
    PCBStr *iter = head;
    int k = 1;
    while(iter->next !=0)
    {
            iter = iter->next;
            k++;
    }
    return k;
}
